//CardGroup.java
package edu.game.chinesepoker;


import java.util.*;

import edu.game.chinesepoker.CardGroupType;

/*
 *@author:
 *2012-7-4
 */
public class CardGroup implements Comparable<CardGroup> {
	private LinkedList<Card> cards;
	private Card signCard = null;
	private CardGroupType type;
	private boolean isWrongType = false;
	private int[] count;

	/*test the parse methods*/
	public static void main (String[] args) {
		LinkedList<Card> cards = new LinkedList<Card>();
		
		
		{//sole
			cards.clear();
			cards.add(new Card('D'));
			CardGroup obj = new CardGroup(cards);
			
			System.out.println ("Cards type: " + obj.getType().toString());
			System.out.println ("sign card: " + obj.getSignCard());
		}
		
		{//rocket
			cards.clear();
			cards.add(new Card('W'));
			cards.add(new Card('w'));
			Collections.sort(cards);
			CardGroup obj = new CardGroup(cards);
			
			System.out.println ("Cards type: " + obj.getType().toString());
			System.out.println ("sign card: " + obj.getSignCard());
		}
		
		{//double
			cards.clear();
			cards.add(new Card(12));
			cards.add(new Card(12));
			CardGroup obj = new CardGroup(cards);
			
			System.out.println ("Cards type: " + obj.getType().toString());
			System.out.println ("sign card: " + obj.getSignCard());				
			
		}
		
		{//test singles 
			cards.clear();
			
			for (int i = 4; i < 7; i++) {
				cards.add(new Card(i));
			}
			cards.add(new Card(3));
			cards.add(new Card(7));
			Collections.sort(cards);
			
			for (Card card : cards)
				System.out.print(card);
			System.out.println("");
			
			CardGroup obj = new CardGroup(cards);
			
	
			System.out.println ("Cards type: " + obj.getType().toString());
			System.out.println ("sign card: " + obj.getSignCard());
		}
		
		{//triples with soles or doubles
			cards.clear();
			
			for (int i = 10; i < 12; i++) {
				cards.add(new Card(i));
				cards.add(new Card(i));
				cards.add(new Card(i));
			}
			
			for (int i = 0; i < 2; i++) {
				cards.add(new Card(9));
				cards.add(new Card(2));
			}
			
			Collections.sort(cards);
			
			for (Card card : cards)
				System.out.print(card);
			System.out.println("");
			
			CardGroup obj = new CardGroup(cards);
			
	
			System.out.println ("Cards type: " + obj.getType().toString());
			System.out.println ("sign card: " + obj.getSignCard());
			
		}
	}
	
	
	public CardGroup(LinkedList<Card> cards) {
		if (cards == null)
			this.cards = null;
		else {
			this.cards = cards;
			this.count = new int[Card.CARD_TYPE_NUM];
			
			// initialize the array.
			for (int i = 0; i < Card.CARD_TYPE_NUM; i++)
				count[i] = 0;

			for (Card card : cards)
				count[card.getVal()]++;
			/*for (int i : count) 
				System.out.print (i + " ");*/

			parseType();
		}
	}
	
	/**
	 * @param cardGroup
	 *            the cards user played out
	 */
	public int compareTo(CardGroup cardGroup) {
		return signCard.compareTo(cardGroup.signCard);
	}
	
	public String toString () {
		return cards.toString();
	}
	
	public CardGroupType getType () {
		return type;
	}
	
	public Card getSignCard() {
		return signCard;
	}

	public int getCardAmount() {
		return cards.size();
	}
	
	public LinkedList<Card> getCards() {
		return cards;
	}
	
	/*parse the type of card group*/
	private void parseType() {
		if (cards.size() == 0)
			type = CardGroupType.WRONG_TYPE;
		else if (cards.size() == 1) {
			type = CardGroupType.SOLE;
			signCard = cards.get(0);
		} else if (cards.size() == 2 && isRocket())
			type = CardGroupType.ROCKET;
		else if (cards.size() == 2 && isDouble())
			type = CardGroupType.DOUBLE;
		else if (!isWrongType && cards.size() == 3 && isTriple())
			type = CardGroupType.TRIPLE;
		else if (!isWrongType && cards.size() == 4 && isBomb())
			type = CardGroupType.BOMB;
		else if (!isWrongType && cards.size() == 4 && isTripleWithSole())
			type = CardGroupType.TRIPLE_SOLE;
		else if (!isWrongType && cards.size() == 5 && isTripleWithDouble())
			type = CardGroupType.TRIPLE_DOUBLE;
		else if (!isWrongType && cards.size() == 6 && isBombWithTwo())
			type = CardGroupType.BOMB_TWO;
		else if (!isWrongType && cards.size() >= 5 && isSoles())
			type = CardGroupType.SOLE_MUL;	
		else if (!isWrongType && cards.size() >= 6 && isDoubles())
			type = CardGroupType.DOUBLE_MUL;
		else if (!isWrongType && cards.size() >= 6 && isTriples())
			type = CardGroupType.TRIPLE_MUL;
		else if (!isWrongType && cards.size() >= 8 && isTriplesWithSoles())
			type = CardGroupType.TRIPLE_SOLE_MUL;
		else if (!isWrongType && cards.size() >= 10 && isTriplesWithDoubles())
			type = CardGroupType.TRIPLE_DOUBLE_MUL;
		else
			type = CardGroupType.WRONG_TYPE;

	}

	private boolean isRocket() {
		if (cards.get(0).getCh() == Card.LKING
				&& cards.get(1).getCh() == Card.KING) {
			signCard = cards.get(1);
			return true;
		}

		return false;
	}

	/* judge double. can't be rocket */
	private boolean isDouble() {

		if (cards.get(0).equals(cards.get(1))) {
			signCard = cards.get(0);
			return true;
		}

		isWrongType = true;

		return false;
	}

	private boolean isTriple() {

		if (cards.get(0).equals(cards.get(1))
				&& cards.get(1).equals(cards.get(2))) {
			signCard = cards.get(0);
			return true;
		}

		isWrongType = true;

		return false;
	}

	private boolean isBomb() {

		if (cards.get(0).equals(cards.get(1))
				&& cards.get(1).equals(cards.get(2))
				&& cards.get(2).equals(cards.get(3))) {
			signCard = cards.get(0);
			return true;
		}

		return false;
	}

	private boolean isTripleWithSole() {
		return isTripleWithCards(1);
	}
	
	private boolean isTripleWithDouble() {
		return isTripleWithCards(2);
	}
	
	private boolean isBombWithTwo () {
		for (int i = 0; i < Card.chToVal('2'); i++) {
			if (count[i] != 0) {
				int bomb = 0;
				int two = 0;
				int signIndex = 0;
				
				for (int j = i; j < Card.chToVal('2'); j++) {
					if (count[j] == 1)
						two++;
					else if (count[j] == 2)
						two += 2;
					else if (count[j] == 4) {
						bomb++;
						signIndex = j;
					}
					else
						return false;
				}
				
				if (two == 2 && bomb == 1) {
					signCard = new Card(signIndex);
					return true;
				}
				return false;
			}//if
		}//for
		return false;
	}
		
	private boolean isSoles() {
		return isSeries(1, 5, 'D', 'A');
	}

	private boolean isDoubles() {
		if (cards.size() % 2 != 0)
			return false;
		return isSeries(2, 3, 'Q', 'A');
	}

	private boolean isTriples() {
		if (cards.size() % 3 != 0)
			return false;
		return isSeries(3, 2, 'K', 'A');
	}
	
	private boolean isTriplesWithSoles() {
		if (cards.size() % 4 != 0)
			return false;
		return isTriplesWithCards(1);
	}
	
	private boolean isTriplesWithDoubles() {
		if (cards.size() % 5 != 0)
			return false;
		return isTriplesWithCards(2);
	}
	
	//help functions.
	private boolean isTripleWithCards(int withNum) {
		int signCardVal = -1;
		int triple = 0;
		int doubleCard = 0;

		for (int i = 0; i < Card.CARD_TYPE_NUM && (doubleCard != 1 || triple != 1); i++) {
			if (count[i] == withNum)
				doubleCard++;
			else if (count[i] == 3) {
				signCardVal = i;
				triple++;
			}
		}

		if (doubleCard == 1 && triple == 1) {
			signCard = new Card(signCardVal);
			return true;
		}

		//isWrongType = true;	//may be soles

		return false;
	}
	
	private boolean isSeries(int mulNum, int len, char biggestStart, char lastCh) {
		for (int i = 0; i <= Card.chToVal(biggestStart); i++) {
			if (count[i] != 0) {
				int num = 0;
				
				for (int j = i; j <= Card.chToVal(lastCh); j++){
					if (count[j] == mulNum)
						num++;
					else if (num >= len) {/* potential doubles */
						for (int k = j; k < Card.CARD_TYPE_NUM; k++)
							if (count[k] != 0) {
								//isWrongType = true;	//may be triples with doubles
								return false;
							}
						signCard = cards.getLast();
						return true;
					} else {
						return false;
					}
				}//for
				
				if (num >= len) {/* potential doubles */
					int startIndex = Card.chToVal(lastCh)+1;
					for (int k = startIndex; k < Card.CARD_TYPE_NUM; k++)
						if (count[k] != 0) {
							isWrongType = true;
							return false;
						}
					signCard = cards.getLast();
					return true;
				}
				return false;
			}//if
		}//for
		return false;
	}
	
	private boolean isTriplesWithCards(int withNum) {
		int triple = 0;
		int withCard = 0;
		int signCardVal = -1;
		int tripleStartIndex = -1;
	
		
		//check triples
		for (int i = 0; i <= Card.chToVal('K'); i++) {
			if (count[i] == 3) {
				tripleStartIndex = i;
				for (int j = i; j <= Card.chToVal('A'); j++) {
					if (count[j] == 3)
						triple++;
					else if (triple >= 2) {
						signCardVal = j-1;
						break;
					}
					else
						return false;	
				}//for
				
				if (triple >= 2 && signCardVal == -1) {/*XXXKKKAAA*/
					signCardVal = Card.chToVal('A');
				}		
				break;
			}//if		
		}
		
		for (int i = 0; i < Card.CARD_TYPE_NUM; i++) {
			if (count[i] == 0)
				continue;
			else if (count[i] == withNum) 
				withCard++;
			else if (count[i] == 3 && tripleStartIndex++ == i )
				continue;
			else
				return false;
		}
		
		if (triple == withCard) {
			signCard = new Card(signCardVal);
			return true;
		}
			
		return false;
	}

}
